#include "filesys/directory.h"
#include <stdio.h>
#include <string.h>
#include <list.h>
#include "filesys/filesys.h"
#include "filesys/inode.h"
#include "threads/malloc.h"
// 3 idiots added begin
#include "threads/thread.h"
// 3 idiots added end

/* Create a directory with space for ENTRY_CNT entries in the
   given SECTOR.  Returns true if successful, false on failure. */
bool dir_create (disk_sector_t sector, disk_sector_t parent_sector)
{
    // 3 idiots added begin
    bool ret_value = true;
    
    // create empty dir
    if (!inode_create (sector, 0, true))
    {
       ret_value = false;
    }
    
    struct dir* new_dir = dir_initialize (sector);
    
    if (!dir_add (new_dir, ".", sector))
    {
       ret_value = false;
    }
    if (!dir_add (new_dir, "..", parent_sector))
    {
       ret_value = false;
    }

    dir_close (new_dir);
	
    return ret_value;
    // 3 idiots added end
}

/* Opens and returns the directory for the given INODE, of which
   it takes ownership.  Returns a null pointer on failure. */
struct dir *
dir_open (struct inode *inode) 
{
  struct dir *dir = calloc (1, sizeof *dir);
  if (inode != NULL && dir != NULL)
    {
      dir->inode = inode;
      dir->pos = 0;
      return dir;
    }
  else
    {
      inode_close (inode);
      free (dir);
      return NULL; 
    }
}

/* Opens the root directory and returns a directory for it.
   Return true if successful, false on failure. */
struct dir *
dir_open_root (void)
{
  return dir_open (inode_open (ROOT_DIR_SECTOR));
}

/* Opens and returns a new directory for the same inode as DIR.
   Returns a null pointer when failure occurs. */
struct dir *
dir_reopen (struct dir *dir) 
{
  return dir_open (inode_reopen (dir->inode));
}

/* Destroys DIR and frees associated resources. */
void
dir_close (struct dir *dir) 
{
  if (dir != NULL)
    {
      inode_close (dir->inode);
      free (dir);
    }
}

/* Returns the inode encapsulated by DIR. */
struct inode *
dir_get_inode (struct dir *dir) 
{
  return dir->inode;
}

/* Searches DIR for a file with the given NAME.
   If successful, returns true, sets *EP to the directory entry
   if EP is non-null, and sets *OFSP to the byte offset of the
   directory entry if OFSP is not null.
   otherwise, returns false and ignores EP and OFSP. */
static bool
lookup (const struct dir *dir, const char *name,
        struct dir_entry *ep, off_t *ofsp) 
{
  struct dir_entry e;
  size_t ofs;
  
  ASSERT (dir != NULL);
  ASSERT (name != NULL);

  for (ofs = 0; inode_read_at (dir->inode, &e, sizeof e, ofs) == sizeof e;
       ofs += sizeof e) 
    if (e.in_use && !strcmp (name, e.name)) 
      {
        if (ep != NULL)
          *ep = e;
        if (ofsp != NULL)
          *ofsp = ofs;
        return true;
      }
  return false;
}

/* Searches DIR for a file with the given NAME
   and returns true if one exists, false otherwise.
   On success, sets *INODE to an inode for the file, otherwise to
   a null pointer.  The caller must close *INODE. */
bool
dir_lookup (const struct dir *dir, const char *name,
            struct inode **inode) 
{
  struct dir_entry e;

  ASSERT (dir != NULL);
  ASSERT (name != NULL);

  if (lookup (dir, name, &e, NULL))
    *inode = inode_open (e.inode_sector);
  else
    *inode = NULL;

  return *inode != NULL;
}

/* Adds a file named NAME to DIR, which must not already contain a
   file by that name.  The file's inode is in sector
   INODE_SECTOR.
   Returns true on success, false on failure.
   Fails if NAME is invalid (i.e. too long) or a disk or memory
   error occurs. */
bool
dir_add (struct dir *dir, const char *name, disk_sector_t inode_sector) 
{
  struct dir_entry e;
  off_t ofs;
  bool success = false;
  
  ASSERT (dir != NULL);
  ASSERT (name != NULL);

  /* Check NAME for validity. */
  if (*name == '\0' || strlen (name) > NAME_MAX)
    return false;

  /* Check NAME is not in use. */
  if (lookup (dir, name, NULL, NULL))
    goto done;

  /* Set OFS to offset of free slot.
     If there are no free slots, then it will be set to the
     current end-of-file.
     
     inode_read_at() will only return a short read at end of file.
     Otherwise, we'd need to verify that we didn't get a short
     read due to something intermittent such as low memory. */
  for (ofs = 0; inode_read_at (dir->inode, &e, sizeof e, ofs) == sizeof e;
       ofs += sizeof e) 
    if (!e.in_use)
      break;

  /* Write slot. */
  e.in_use = true;
  strlcpy (e.name, name, sizeof e.name);
  e.inode_sector = inode_sector;
  success = inode_write_at (dir->inode, &e, sizeof e, ofs) == sizeof e;

 done:
  return success;
}

/* Removes any entry for NAME in DIR.
   Returns true if successful, false on failure,
   which occurs only if there is no file with the given NAME. */
bool
dir_remove (struct dir *dir, const char *name) 
{
  struct dir_entry e;
  struct inode *inode = NULL;
  bool success = false;
  off_t ofs;

  ASSERT (dir != NULL);
  ASSERT (name != NULL);

  /* find directory entry. */
  if (!lookup (dir, name, &e, &ofs))
    goto done;

  /* open inode. */
  inode = inode_open (e.inode_sector);
  if (inode == NULL)
    goto done;

  /* erase directory entry. */
  e.in_use = false;
  if (inode_write_at (dir->inode, &e, sizeof e, ofs) != sizeof e) 
    goto done;

  /* remove inode. */
  inode_remove (inode);
  success = true;

 done:
  inode_close (inode);
  return success;
}

/* Reads the next directory entry in DIR and stores the name in
   NAME.  Returns true if successful, false if the directory
   contains no more entries. */
bool
dir_readdir (struct dir *dir, char name[NAME_MAX + 1])
{
  struct dir_entry e;
  // 3 idiots added begin
  off_t temp_ofs = (off_t) sizeof (struct dir_entry) * 2;
  if (dir->pos < temp_ofs)
  {
     dir->pos = temp_ofs;
  }
  // 3 idiots added end

  while (inode_read_at (dir->inode, &e, sizeof e, dir->pos) == sizeof e) 
    {
      dir->pos += sizeof e;
      if (e.in_use)
        {
          strlcpy (name, e.name, NAME_MAX + 1);
          return true;
        } 
    }
  return false;
}

// 3 idiots added begin
bool dir_get_dir_path_and_file_name (char* arg_dir_path, char** rtn_path, char** rtn_name)
{
   char* temp_path = dir_get_temp_path (arg_dir_path);
   char* temp_spr = strrchr (temp_path, '/');
   int dir_path_length = strlen (arg_dir_path);

   if ((int) temp_spr == dir_path_length)
   {
      temp_path[dir_path_length] = HEX_0;
   }
   if (temp_spr == NULL)
   {
      *rtn_name = temp_path;
      *rtn_path = malloc(1);
      (*rtn_path)[0] = HEX_0;
      return true;
   }
   else if (temp_spr == temp_path)
   {
      *rtn_name = malloc (dir_path_length);
      strlcpy (*rtn_name, arg_dir_path + 1, dir_path_length);

      *rtn_path = malloc(2);
      (*rtn_path)[0] = '/';
      (*rtn_path)[1] = HEX_0;
   }
   else
   {
      int temp_n_length = dir_path_length - (temp_spr - temp_path) + 1;
      *rtn_name = malloc (temp_n_length);
      strlcpy (*rtn_name, temp_spr + 1, temp_n_length);

      int temp_p_length = temp_spr - temp_path + 1;
      *rtn_path = malloc (temp_p_length);
      strlcpy (*rtn_path, temp_path, temp_p_length);
   }

   free (temp_path);
   return true;
}

struct dir* dir_initialize (disk_sector_t arg_sector)
{
    struct dir* temp_dir = (struct dir*) malloc (sizeof (struct dir));
    temp_dir->inode = inode_open (arg_sector);
    temp_dir->pos = 0;
    return temp_dir;
}

char* dir_get_temp_path (const char* arg_path)
{
    char* temp_path = malloc (strlen (arg_path) + 1);
    strlcpy (temp_path, arg_path, strlen (arg_path) + 1);
    return temp_path;
}

struct dir* dir_get_directory (const char *path)
{
    if (path == NULL)
    {
       return thread_get_current_dir ();
    }
    
    struct dir* temp_dir = dir_get_by_path (path[0]);
    char* temp_path = dir_get_temp_path (path);
    char* save_ptr;
    char* next = strtok_r (temp_path, "/", &save_ptr);  
    struct inode* next_inode = NULL;

    for (; next != NULL; next = strtok_r (NULL, "/", &save_ptr))
    {
	if (dir_lookup (temp_dir, next, &next_inode))
	{
	    struct dir* next_dir = dir_open (next_inode);
			
	    dir_close (temp_dir);
	    temp_dir = next_dir;
	 }
	 else
	 {
	    dir_close (temp_dir);
	    return NULL;
	 }
     }
     free (temp_path);
     return temp_dir;
}

struct dir* dir_get_by_path (char arg_ch)
{
    if (arg_ch == '/')
    {
       return dir_open_root ();
    }
    else
    {
       return dir_reopen (thread_get_current_dir ());
    }
}

bool dir_none (struct dir* arg_dir) 
{
    off_t old_pos = arg_dir->pos;
    char* dir_entry_name = malloc (NAME_MAX + 1);

    arg_dir->pos = 0;
    bool empty = !dir_readdir (arg_dir, dir_entry_name);

    arg_dir->pos = old_pos;
    free (dir_entry_name);

    return empty;
}
// 3 idiots added end
